﻿using System;
using System.Diagnostics;
using System.IO;

namespace SfsTools.Core
{
    /// <summary>
    /// Represents a directory on the <see cref="FileSystem"/>.
    /// </summary>
    internal class Directory : FileSystemObject, IDirectory
    {
        #region Properties

        /// <summary>
        /// Contains the count of present (sub)directories.
        /// </summary>
        private int FPresentDirectoryCount;
        /// <summary>
        /// Contains the count of present files.
        /// </summary>
        private int FPresentFileCount;
        /// <summary>
        /// Contains the list of subdirectories.
        /// </summary>
        private System.Collections.Generic.List<Directory> FDirectories = new System.Collections.Generic.List<Directory>();
        /// <summary>
        /// Contains the list of files.
        /// </summary>
        private System.Collections.Generic.List<File> FFiles = new System.Collections.Generic.List<File>();
        /// <summary>
        /// Contains the subdirectory-by-name lookup.
        /// </summary>
        private System.Collections.Generic.Dictionary<String, FileSystemObject> FNameLookup = new System.Collections.Generic.Dictionary<String, FileSystemObject>();

        #endregion

        #region Methods

        #region Misc

        /// <summary>
        /// Returns an array containing all <see cref="Directory">subdirectories</see>.
        /// </summary>
        /// <returns>An array containing all <see cref="Directory">subdirectories</see>.</returns>
        [DebuggerStepThrough]
        public Directory[] GetDirectories()
        {
            return GetDirectories(false);
        }

        /// <summary>
        /// Returns an array containing all <see cref="Directory">subdirectories</see>.
        /// </summary>
        /// <param name="includeDeleted">Specifies whether to include deleted <see cref="Directory">subdirectories</see> as well.</param>
        /// <returns>An array containing all <see cref="Directory">subdirectories</see>.</returns>
        [DebuggerStepThrough]
        public Directory[] GetDirectories(bool includeDeleted)
        {
            if (includeDeleted)
            {
                return FDirectories.ToArray();
            }
            else
            {
                System.Collections.Generic.List<Directory> list = new System.Collections.Generic.List<Directory>();
                foreach (Directory directory in FDirectories)
                {
                    if (!directory.IsDeleted)
                        list.Add(directory);
                }
                return list.ToArray();
            }
        }

        /// <summary>
        /// Returns an array containing all <see cref="File"/>s.
        /// </summary>
        /// <returns>An array containing all <see cref="File"/>s.</returns>
        [DebuggerStepThrough]
        public File[] GetFiles()
        {
            return GetFiles(false);
        }

        /// <summary>
        /// Returns an array containing all <see cref="File"/>s.
        /// </summary>
        /// <param name="includeDeleted">Specifies whether to include deleted <see cref="File"/>s as well.</param>
        /// <returns>An array containing all <see cref="File"/>s.</returns>
        [DebuggerStepThrough]
        public File[] GetFiles(bool includeDeleted)
        {
            if (includeDeleted)
            {
                return FFiles.ToArray();
            }
            else
            {
                System.Collections.Generic.List<File> list = new System.Collections.Generic.List<File>();
                foreach (File file in FFiles)
                {
                    if (!file.IsDeleted)
                        list.Add(file);
                }
                return list.ToArray();
            }
        }

        /// <summary>
        /// Returns an array containing all <see cref="FileSystemObject"/>s.
        /// </summary>
        /// <returns>An array containing all <see cref="FileSystemObject"/>s.</returns>
        [DebuggerStepThrough]
        public FileSystemObject[] GetObjects()
        {
            return GetObjects(false);
        }

        /// <summary>
        /// Returns an array containing all <see cref="FileSystemObject"/>s.
        /// </summary>
        /// <param name="includeDeleted">Specifies whether to include deleted <see cref="FileSystemObject"/>s as well.</param>
        /// <returns>An array containing all <see cref="FileSystemObject"/>s.</returns>
        [DebuggerStepThrough]
        public FileSystemObject[] GetObjects(bool includeDeleted)
        {
            if (includeDeleted)
            {
                System.Collections.Generic.List<FileSystemObject> list = new System.Collections.Generic.List<FileSystemObject>();
                foreach (Directory directory in FDirectories)
                {
                    list.Add(directory);
                }
                foreach (File file in FFiles)
                {
                    list.Add(file);
                }
                return list.ToArray();                
            }
            else
            {
                System.Collections.Generic.List<FileSystemObject> list = new System.Collections.Generic.List<FileSystemObject>();
                foreach (Directory directory in FDirectories)
                {
                    if (!directory.IsDeleted)
                        list.Add(directory);
                }
                foreach (File file in FFiles)
                {
                    if (!file.IsDeleted)
                        list.Add(file);
                }
                return list.ToArray();
            }
        }

        /// <summary>
        /// Creates a <see cref="IEntryMoveCallback"/> instance that is used when the <see cref="IndexArea"/> is compacted.
        /// </summary>
        /// <returns></returns>
        [DebuggerStepThrough]
        public IEntryMoveCallback GetRelocationCallback()
        {
            RelocateCallback rc = new RelocateCallback();
            rc.Collect(this);
            return rc;
        }
        
        /// <summary>
        /// Returns a <see cref="String"/> that represents the current <see cref="Directory"/>.
        /// </summary>
        /// <returns>A <see cref="String"/> that represents the current <see cref="Directory"/>.</returns>
        [DebuggerStepThrough]
        public override string ToString()
        {
            return String.Concat('{',
                String.Format(Translation.Resources.GetString(typeof(Directory).Name),
                GetFullPath(), FDirectories.Count, FFiles.Count), '}');
        }

        /// <summary>
        /// Returns a value indicating whether <paramref name="name"/> is defined to a <see cref="FileSystemObject"/>.
        /// </summary>
        /// <param name="name">The name to look up.</param>
        /// <returns></returns>
        [DebuggerStepThrough]
        public bool Contains(String name)
        {
            return FNameLookup.ContainsKey(name);
        }

        /// <summary>
        /// Accepts a visitor.
        /// </summary>
        /// <param name="visitor">The visitor.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="visitor"/> is <see langword="null"/>.</exception>
        [DebuggerStepThrough]
        public override void AcceptVisitor(IFileSystemObjectVisitor visitor)
        {
            if (visitor == null)
                throw new ArgumentNullException("visitor");
            visitor.VisitDirectory(this);
        }
        /// <summary>
        /// Accepts a visitor.
        /// </summary>
        /// <param name="visitor">The visitor.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="visitor"/> is <see langword="null"/>.</exception>
        [DebuggerStepThrough]
        public override void AcceptVisitor(IFileSystemObjectInternalVisitor visitor)
        {
            if (visitor == null)
                throw new ArgumentNullException("visitor");
            visitor.VisitDirectory(this);
        }

        /// <summary>
        /// Calculates the full path for a new <see cref="FileSystemObject"/>.
        /// </summary>
        /// <param name="name">The name of the <see cref="FileSystemObject"/>.</param>
        /// <returns>The full path for a new <see cref="FileSystemObject"/>.</returns>
        [DebuggerStepThrough]
        private String GetPathForNewFileSystemObject(String name)
        {
            return FParent == null ? String.Concat(Path.DirectorySeparatorChar, name) : String.Concat(GetFullPath(), Path.DirectorySeparatorChar, name);
        }

        #endregion

        #region Create subdirectory

        /// <summary>
        /// Creates a subdirectory.
        /// </summary>
        /// <param name="name">The name of the subdirectory.</param>
        /// <returns>An <see cref="IDirectory"/> instance representing the subdirectory.</returns>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">The argument <paramref name="name"/> contains invalid characters.</exception>
        /// <exception cref="IndexAreaFullException">Cannot allocate entries because the index area is full.</exception>
        /// <exception cref="System.IO.PathTooLongException">The full path is too long.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        [DebuggerStepThrough]
        public Directory CreateDirectory(String name)
        {
            PathValidator.ValidateDirectoryName(name);
            // Check if the user tries to create a directory that was previously deleted or already exists.
            if (FNameLookup.ContainsKey(name))
            {
                FileSystemObject fso = FNameLookup[name];
                InvalidateOrResurrectDirectory visitor = new InvalidateOrResurrectDirectory(this);
                fso.AcceptVisitor(visitor);
                return visitor.GetDirectory();
            }
            int index = FFileSystem.IndexArea.AllocateDirectory(GetPathForNewFileSystemObject(name));
            Directory directory = new Directory(FFileSystem, this, name, index);
            FNameLookup.Add(name, directory);
            FDirectories.Add(directory);
            FPresentDirectoryCount++;
            return directory;
        }

        /// <summary>
        /// Recreates a subdirectory that was previously deleted, but cannot be simply resurrected.
        /// </summary>
        /// <param name="name">The name of the subdirectory.</param>
        /// <returns>An <see cref="IDirectory"/> instance representing the subdirectory.</returns>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">The argument <paramref name="name"/> contains invalid characters.</exception>
        /// <exception cref="IndexAreaFullException">Cannot allocate entries because the index area is full.</exception>
        /// <exception cref="System.IO.PathTooLongException">The full path is too long.</exception>
        /// <exception cref="CannotResurrectFileSystemObjectException">The directory cannot be recreated.</exception>
        [DebuggerStepThrough]
        private Directory Recreate(String name)
        {
            PathValidator.ValidateDirectoryName(name);
            // Check if the user tries to create a directory that was previously deleted or already exists.
            Debug.Assert(FNameLookup.ContainsKey(name));
            FileSystemObject fso = FNameLookup[name];
            if (fso is File)
            {
                // Dir was deleted, then a file was created with the same name.
                throw new FileAlreadyExistsException();
            }
            Directory directory = (Directory)fso;
            Debug.Assert(directory.FIsDeleted && (directory.FIndexAreaEntryIndex < 0));
            int index = FFileSystem.IndexArea.AllocateDirectory(GetPathForNewFileSystemObject(name));
            directory.FIndexAreaEntryIndex = index;
            FPresentDirectoryCount++;
            return directory;
        }

        #endregion

        #region Create file

        /// <summary>
        /// Creates or overwrites a file while reserving space for it in the data area.
        /// </summary>
        /// <param name="name">The name of the file.</param>
        /// <param name="length">Specifies the maximum length of the file that is guaranteed to be available, or <em>-1</em> for no allocation.</param>
        /// <returns>An <see cref="IFile"/> instance representing the file.</returns>
        /// <remarks>The <see cref="IFile"/> can grow larger than <paramref name="length"/>, but that space is not guaranteed to be available.</remarks>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">The argument <paramref name="name"/> contains invalid characters.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The argument <paramref name="length"/> is negative.</exception>
        /// <exception cref="IndexAreaFullException">Cannot create file because the index area is full.</exception>
        /// <exception cref="DataAreaFullException">Cannot create file because the data area is full.</exception>
        /// <exception cref="System.IO.PathTooLongException">The full path is too long.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        public File CreateFile(String name, Int64 length)
        {
            PathValidator.ValidateFileName(name);
            // Check if the user tries to create a directory that was previously deleted or already exists.
            if (FNameLookup.ContainsKey(name))
            {
                FileSystemObject fso = FNameLookup[name];
                InvalidateOrResurrectFile visitor = new InvalidateOrResurrectFile(this);
                fso.AcceptVisitor(visitor);
                return visitor.GetFile();
            }
            int index = FFileSystem.IndexArea.AllocateFile(GetPathForNewFileSystemObject(name), length);
            File file = new File(FFileSystem, this, name, index);
            FNameLookup.Add(name, file);
            FFiles.Add(file);
            FPresentFileCount++;
            return file;
        }

        #endregion

        #region Delete

        /// <summary>
        /// Deletes this <see cref="IDirectory"/>.
        /// </summary>
        /// <exception cref="InvalidOperationException">The <see cref="IFileSystemObject"/> is already deleted.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        [DebuggerStepThrough]
        public override void Delete()
        {
            Delete(false);
        }

        /// <summary>
        /// Deletes this <see cref="IDirectory"/>.
        /// </summary>
        /// <param name="recurse">Specifies whether to delete any files and subdirectories as well.</param>
        /// <exception cref="InvalidOperationException">The directory is not empty.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        public void Delete(bool recurse)
        {
            CheckAlive();
            if (FIsDeleted)
            {
                // Already deleted.
                throw new DirectoryAlreadyDeletedException(GetFullPath());
            }
            if (recurse)
            {
                // (bug) ToArray required because deletion modifies the underlying collection.
                foreach (Directory directory in FDirectories.ToArray())
                {
                    if (!directory.IsDeleted)
                        directory.Delete(recurse);
                }
                // (bug) ToArray required because deletion modifies the underlying collection.
                foreach (File file in FFiles.ToArray())
                {
                    if (!file.IsDeleted)
                        file.Delete();
                }
            }
            // Disallow deletion if this has present directories and files.
            if ((FPresentDirectoryCount > 0) || (FPresentFileCount > 0))
            {
                throw new DirectoryNotEmptyException(GetFullPath());
            }
            // If we have an index, delete it, else (in case of intermediate directories) just set the deleted flag.
            if (FIndexAreaEntryIndex > 0)
                FFileSystem.IndexArea.DeleteDirectory(FIndexAreaEntryIndex, new RemoveDirectoryCallback(this));
            FIsDeleted = true;
            // Decrement directory count on parent.
            if (FParent != null)
                FParent.FPresentDirectoryCount--;
            // V1: resurrection is not supported.
            Debug.Assert(FParent != null);
            FParent.FDirectories.Remove(this);
            FParent.FNameLookup.Remove(FName);
            Kill();
        }

        /// <summary>
        /// Removes <paramref name="file"/> from the list of files.
        /// </summary>
        /// <param name="file">The file to remove.</param>
        /// <param name="index">The index area index of the file to remove.</param>
        [DebuggerStepThrough]
        public void DeleteFile(File file, int index)
        {
            Debug.Assert(file.Parent == this);
            // If we have an index, delete it, else (in case of intermediate directories) just set the deleted flag.
            FFileSystem.IndexArea.DeleteFile(index, new RemoveFileCallback(file));
            //FFileSystem.DataArea.
            FIsDeleted = true;
            // Decrement directory count on parent.
            FPresentFileCount--;
            // V1: resurrection is not supported.
            FFiles.Remove(file);
            FNameLookup.Remove(file.Name);
        }

        /// <summary>
        /// Deletes the <see cref="IFileSystemObject"/> with the specified <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The name of the <see cref="IFileSystemObject"/> to delete.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="IOException">An I/O exception occurred.</exception>
        /// <exception cref="PathNotFoundException">The specified path cannot be found.</exception>
        public void Delete(String name)
        {
            FileSystemObject fso = Resolve(name);
            fso.Delete();
        }

        /// <summary>
        /// Deletes the <see cref="IDirectory"/> with the specified <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The name of the <see cref="IDirectory"/> to delete.</param>
        /// <param name="recurse">Specifies whether to delete any files and subdirectories as well.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="IOException">An I/O exception occurred.</exception>
        /// <exception cref="DirectoryNotFoundException">The specified path cannot be found.</exception>
        /// <exception cref="InvalidOperationException">The directory is not empty, and <paramref name="recurse"/> was not specified.</exception>
        [DebuggerStepThrough]
        public void Delete(String name, bool recurse)
        {
            Directory directory = Resolve(name) as Directory;
            if (directory == null)
                throw new DirectoryNotFoundException();
            directory.Delete(recurse);
        }

        #endregion

        #region Rename

        /// <summary>
        /// Renames this directory to the specified <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The new name for the directory.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">The argument <paramref name="name"/> contains invalid characters.</exception>
        /// <exception cref="InvalidOperationException">The parent directory already contains a directory with <paramref name="name"/>.</exception>
        /// <exception cref="IndexAreaFullException">Cannot allocate entries because the index area is full.</exception>
        /// <exception cref="System.IO.PathTooLongException">The full path is too long.</exception>
        public override void Rename(String name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            CheckAlive();
            // TODO: InvOpExc raised for two separate reasons.
            if (FIsDeleted)
                throw new DirectoryAlreadyDeletedException(GetFullPath());
            PathValidator.ValidateDirectoryName(name);
            if (FName == name)
                return;
            if (FParent.FNameLookup.ContainsKey(name))
                throw new CannotRenameDirectoryException(GetFullPath());
            // Must have an index.
            // TODO: not necessarily. intermediate directories may not have an index, but the user should still be able to rename them.
            Debug.Assert(FIndexAreaEntryIndex > 0);
            Debug.Assert(FParent != null);
            FIndexAreaEntryIndex = FFileSystem.IndexArea.RenameDirectory(FIndexAreaEntryIndex, String.Concat(FParent.GetFullPath(), name));
            FParent.FNameLookup.Remove(FName);
            FName = name;
            FParent.FNameLookup.Add(FName, this);
        }

        #endregion

        #region Resurrect

        /// <summary>
        /// Restores this <see cref="IDirectory"/> that was previously deleted.
        /// </summary>
        /// <exception cref="InvalidOperationException">The directory is not deleted.</exception>
        /// <exception cref="IOException">An I/O exception occurred.</exception>
        /// <exception cref="IndexAreaFullException">Cannot create directory because the index area is full.</exception>
        //[DebuggerStepThrough]
        public override void Resurrect()
        {
            CheckAlive();
            if (!FIsDeleted)
                throw new DirectoryNotDeletedException(GetFullPath());
            if (FIndexAreaEntryIndex > 0)
            {
                // A simple resurrection will do.
                //FFileSystem.IndexArea.ResurrectDirectory(FIndexAreaEntryIndex);
                FIsDeleted = false;
                // Register with parent.
                Debug.Assert(FParent != null);
                FParent.FPresentDirectoryCount++;
                FParent.FDirectories.Add(this);
            }
            else
            {
                // Cannot simply resurrect, must completely recreate.
                Debug.Assert(FParent != null);
                Directory dir = FParent.Recreate(FName);
                Debug.Assert(dir != null);
            }
        }

        /// <summary>
        /// Restores this <see cref="IDirectory"/> that was previously deleted.
        /// </summary>
        /// <param name="recurse">Specifies whether to restore any files and subdirectories as well.</param>
        /// <exception cref="InvalidOperationException">The directory is not deleted.</exception>
        /// <exception cref="IOException">An I/O exception occurred.</exception>
        /// <exception cref="IndexAreaFullException">Cannot create directory because the index area is full.</exception>
        [DebuggerStepThrough]
        public void Resurrect(bool recurse)
        {
            // Resurrect this directory first.
            Resurrect();
            // And resurrect subdirectories, if required.
            if (recurse)
            {
                foreach (Directory directory in FDirectories)
                {
                    if (directory.FIsDeleted)
                    {
                        directory.Resurrect(recurse);
                    }
                }
            }
        }

        #endregion

        #region Resolve name

        /// <summary>
        /// Resolves the specified path to a <see cref="FileSystemObject"/>.
        /// </summary>
        /// <param name="name">The name of the <see cref="FileSystemObject"/>.</param>
        /// <returns>The resolved <see cref="FileSystemObject"/>.</returns>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="PathNotFoundException">The specified path cannot be found.</exception>
        [DebuggerStepThrough]
        public FileSystemObject Resolve(String name)
        {
            if (name == null)
                throw new ArgumentNullException("name");
            if (name.StartsWith(new String(Path.DirectorySeparatorChar, 1)))
            {
                // Absolute path.
                String[] components = name.Split(new Char[] { Path.DirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
                Directory directory = (Directory)FFileSystem.RootDirectory;
                System.Collections.Generic.Stack<Directory> stack = new System.Collections.Generic.Stack<Directory>();
                // Check and resolve each component.
                for (int i = 0; i < components.Length; i++)
                {
                    if (components[i] == new String('.', 1))
                    {
                        // '.' entry, current directory.
                        continue;
                    }
                    else if (components[i] == new String('.', 2))
                    {
                        // '..' entry, parent directory. Remove last entry unless its the root.
                        if (stack.Count > 0)
                            directory = stack.Pop();
                        continue;
                    }
                    else
                    {
                        // Real entry. Resolve using current directory.
                        FileSystemObject fso;
                        if (!directory.FNameLookup.TryGetValue(components[i], out fso))
                            //throw new DirectoryNotFoundException(name);
                            throw new PathNotFoundException();
                        if (i == components.Length - 1)
                        {
                            // Accept Directory or File.
                            return fso;
                        }
                        else
                        {
                            // Accept Directory only.
                            Directory directory2 = fso as Directory;
                            if (directory2 == null)
                            {
                                // Path specifies a file, not a directory.
                                throw new DirectoryNotFoundException(name);
                            }
                            stack.Push(directory);
                            directory = directory2;
                        }
                    }
                }
                return directory;
            }
            else
            {
                // Relative path.
                if (FParent == null)
                    name = String.Concat(GetFullPath(), name);
                else
                    name = String.Concat(GetFullPath(), Path.DirectorySeparatorChar, name);
                return Resolve(name);
            }
        }

        #endregion

        #region Add subdirectory

        /// <summary>
        /// Creates a subdirectory that is already on the <see cref="FileSystem"/>.
        /// </summary>
        /// <param name="directory">The <see cref="Directory"/> instance representing the subdirectory.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="directory"/> is <see langword="null"/>.</exception>
        /// <remarks>Use this method only when reading the contents of the index area.</remarks>
        [DebuggerStepThrough]
        public Directory Add(Directory directory)
        {
            if (directory == null)
                throw new ArgumentNullException("directory");
            // TODO: what if the user tries to create a directory that was previously deleted?
            if (FNameLookup.ContainsKey(directory.Name))
                throw new DirectoryAlreadyExistsException(directory.GetFullPath());
            FNameLookup.Add(directory.Name, directory);
            FDirectories.Add(directory);
            if (!directory.IsDeleted)
                FPresentDirectoryCount++;
            return directory;
        }

        /// <summary>
        /// Creates a file that is already on the <see cref="FileSystem"/>.
        /// </summary>
        /// <param name="file">The <see cref="File"/> instance representing the file.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="file"/> is <see langword="null"/>.</exception>
        /// <remarks>Use this method only when reading the contents of the index area.</remarks>
        [DebuggerStepThrough]
        public File Add(File file)
        {
            if (file == null)
                throw new ArgumentNullException("file");
            // TODO: what if the user tries to create a file that was previously deleted?
            if (FNameLookup.ContainsKey(file.Name))
                throw new FileAlreadyExistsException(file.GetFullPath());
            FNameLookup.Add(file.Name, file);
            FFiles.Add(file);
            if (!file.IsDeleted)
                FPresentFileCount++;
            return file;
        }

        #endregion

        #endregion

        #region Constructors
        /// <overloads>Initializes a new instance of <see cref="Directory"/>.</overloads>
        /// <summary>
        /// Initializes a new instance of <see cref="Directory"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        [DebuggerStepThrough]
        protected Directory(FileSystem fs)
            : base(fs)
        {
            FIndexAreaEntryIndex = -1;
        }
        /// <summary>
        /// Initializes a new instance of <see cref="Directory"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        /// <param name="parent">The parent directory.</param>
        /// <param name="name">The name of this directory.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="parent"/> is <see langword="null"/>.</exception>
        [DebuggerStepThrough]
        public Directory(FileSystem fs, Directory parent, String name)
            : base(fs, parent, name)
        {
        }
        /// <summary>
        /// Initializes a new instance of <see cref="Directory"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        /// <param name="parent">The parent directory.</param>
        /// <param name="name">The name of this directory.</param>
        /// <param name="index">The zero-based index of the index area entry.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="parent"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The argument <paramref name="index"/> is out of valid range.</exception>
        [DebuggerStepThrough]
        public Directory(FileSystem fs, Directory parent, String name, int index)
            : base(fs, parent, name, index)
        {
        }
        /// <summary>
        /// Initializes a new instance of <see cref="Directory"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        /// <param name="parent">The parent directory.</param>
        /// <param name="name">The name of this directory.</param>
        /// <param name="deleted">Specifies whether this <see cref="Directory"/> is deleted.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="parent"/> is <see langword="null"/>.</exception>
        [DebuggerStepThrough]
        public Directory(FileSystem fs, Directory parent, String name, bool deleted)
            : base(fs, parent, name, deleted)
        {
        }
        /// <summary>
        /// Initializes a new instance of <see cref="Directory"/>.
        /// </summary>
        /// <param name="fs">The file system.</param>
        /// <param name="parent">The parent directory.</param>
        /// <param name="name">The name of this directory.</param>
        /// <param name="index">The zero-based index of the index area entry.</param>
        /// <param name="deleted">Specifies whether this <see cref="Directory"/> is deleted.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="parent"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The argument <paramref name="index"/> is out of valid range.</exception>
        [DebuggerStepThrough]
        public Directory(FileSystem fs, Directory parent, String name, int index, bool deleted)
            : base(fs, parent, name, index, deleted)
        {
        }
        #endregion

        #region IDirectory Members

        /// <summary>
        /// Resolves the specified path to a <see cref="IFileSystemObject"/>.
        /// </summary>
        /// <param name="name">The relative or absolute path to resolve.</param>
        /// <returns>The <see cref="IFileSystemObject"/> specified by <paramref name="name"/>.</returns>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="PathNotFoundException">The specified <see cref="IFileSystemObject"/> cannot be found.</exception>
        [DebuggerStepThrough]
        IFileSystemObject IDirectory.Resolve(String name)
        {
            return Resolve(name);
        }

        /// <summary>
        /// Returns an array containing all <see cref="IDirectory">subdirectories</see>.
        /// </summary>
        /// <returns>An array containing all <see cref="IDirectory">subdirectories</see>.</returns>
        [DebuggerStepThrough]
        IDirectory[] IDirectory.GetDirectories()
        {
            return GetDirectories();
        }

        /// <summary>
        /// Returns an array containing all <see cref="IDirectory">subdirectories</see>.
        /// </summary>
        /// <param name="includeDeleted">Specifies whether to include deleted <see cref="IDirectory">subdirectories</see> as well.</param>
        /// <returns>An array containing all <see cref="IDirectory">subdirectories</see>.</returns>
        [DebuggerStepThrough]
        IDirectory[] IDirectory.GetDirectories(bool includeDeleted)
        {
            return GetDirectories(includeDeleted);
        }

        /// <summary>
        /// Returns an array containing all <see cref="IFile"/>s.
        /// </summary>
        /// <returns>An array containing all <see cref="IFile"/>s.</returns>
        [DebuggerStepThrough]
        IFile[] IDirectory.GetFiles()
        {
            return GetFiles();
        }

        /// <summary>
        /// Returns an array containing all <see cref="IFile"/>s.
        /// </summary>
        /// <param name="includeDeleted">Specifies whether to include deleted <see cref="IFile"/>s as well.</param>
        /// <returns>An array containing all <see cref="IFile"/>s.</returns>
        [DebuggerStepThrough]
        IFile[] IDirectory.GetFiles(bool includeDeleted)
        {
            return GetFiles(includeDeleted);
        }

        /// <summary>
        /// Returns an array containing all <see cref="IFileSystemObject"/>s.
        /// </summary>
        /// <returns>An array containing all <see cref="IFileSystemObject"/>s.</returns>
        [DebuggerStepThrough]
        IFileSystemObject[] IDirectory.GetObjects()
        {
            return GetObjects();
        }

        /// <summary>
        /// Returns an array containing all <see cref="IFileSystemObject"/>s.
        /// </summary>
        /// <param name="includeDeleted">Specifies whether to include deleted <see cref="IFileSystemObject"/>s as well.</param>
        /// <returns>An array containing all <see cref="IFileSystemObject"/>s.</returns>
        [DebuggerStepThrough]
        IFileSystemObject[] IDirectory.GetObjects(bool includeDeleted)
        {
            return GetObjects(includeDeleted);
        }

        /// <summary>
        /// Creates a subdirectory.
        /// </summary>
        /// <param name="name">The name of the subdirectory.</param>
        /// <returns>An <see cref="IDirectory"/> instance representing the subdirectory.</returns>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">The argument <paramref name="name"/> contains invalid characters.</exception>
        /// <exception cref="IndexAreaFullException">Cannot create directory because the index area is full.</exception>
        /// <exception cref="System.IO.PathTooLongException">The full path is too long.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        [DebuggerStepThrough]
        IDirectory IDirectory.CreateDirectory(String name)
        {
            return CreateDirectory(name);
        }

        /// <overloads>Creates or overwrites a file.</overloads>
        /// <summary>
        /// Creates or overwrites a file.
        /// </summary>
        /// <param name="name">The name of the file.</param>
        /// <returns>An <see cref="IFile"/> instance representing the subdirectory.</returns>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">The argument <paramref name="name"/> contains invalid characters.</exception>
        /// <exception cref="IndexAreaFullException">Cannot create file because the index area is full.</exception>
        /// <exception cref="System.IO.PathTooLongException">The full path is too long.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        [DebuggerStepThrough]
        IFile IDirectory.CreateFile(String name)
        {
            return CreateFile(name, -1);
        }

        /// <summary>
        /// Creates or overwrites a file while reserving space for it in the data area.
        /// </summary>
        /// <param name="name">The name of the file.</param>
        /// <param name="length">Specifies the maximum length of the file that is guaranteed to be available.</param>
        /// <returns>An <see cref="IFile"/> instance representing the subdirectory.</returns>
        /// <remarks>The <see cref="IFile"/> can grow larger than <paramref name="length"/>, but that space is not guaranteed to be available.</remarks>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="ArgumentException">The argument <paramref name="name"/> contains invalid characters.</exception>
        /// <exception cref="ArgumentOutOfRangeException">The argument <paramref name="length"/> is negative.</exception>
        /// <exception cref="IndexAreaFullException">Cannot create file because the index area is full.</exception>
        /// <exception cref="DataAreaFullException">Cannot create file because the data area is full.</exception>
        /// <exception cref="System.IO.PathTooLongException">The full path is too long.</exception>
        /// <exception cref="System.IO.IOException">An I/O exception occurred.</exception>
        [DebuggerStepThrough]
        IFile IDirectory.CreateFile(String name, Int64 length)
        {
            if (length < 0)
                throw new ArgumentOutOfRangeException("length");
            return CreateFile(name, length);
        }

        /// <summary>
        /// Deletes the <see cref="IDirectory"/> with the specified <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The name of the <see cref="IDirectory"/> to delete.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="IOException">An I/O exception occurred.</exception>
        /// <exception cref="DirectoryNotFoundException">The specified directory cannot be found.</exception>
        [DebuggerStepThrough]
        void IDirectory.Delete(String name)
        {
            Delete(name);
        }

        /// <summary>
        /// Deletes this <see cref="IDirectory"/>.
        /// </summary>
        /// <param name="recurse">Specifies whether to delete any files and subdirectories as well.</param>
        /// <exception cref="InvalidOperationException">The directory is not empty, and <paramref name="recurse"/> was not specified.</exception>
        [DebuggerStepThrough]
        void IDirectory.Delete(bool recurse)
        {
            Delete(recurse);
        }

        /// <summary>
        /// Deletes the <see cref="IDirectory"/> with the specified <paramref name="name"/>.
        /// </summary>
        /// <param name="name">The name of the <see cref="IDirectory"/> to delete.</param>
        /// <param name="recurse">Specifies whether to delete any files and subdirectories as well.</param>
        /// <exception cref="ArgumentNullException">The argument <paramref name="name"/> is <see langword="null"/>.</exception>
        /// <exception cref="IOException">An I/O exception occurred.</exception>
        /// <exception cref="DirectoryNotFoundException">The specified directory cannot be found.</exception>
        /// <exception cref="InvalidOperationException">The directory is not empty, and <paramref name="recurse"/> was not specified.</exception>
        [DebuggerStepThrough]
        void IDirectory.Delete(String name, bool recurse)
        {
            Delete(name, recurse);
        }

#if false
        /// <summary>
        /// Restores this <see cref="IDirectory"/> that was previously deleted.
        /// </summary>
        /// <param name="recurse">Specifies whether to restore any files and subdirectories as well.</param>
        /// <exception cref="InvalidOperationException">The directory is not deleted.</exception>
        /// <exception cref="IOException">An I/O exception occurred.</exception>
        /// <exception cref="IndexAreaFullException">Cannot create directory because the index area is full.</exception>
        [DebuggerStepThrough]
        void IDirectory.Resurrect(bool recurse)
        {
            Resurrect(recurse);
        }
#endif

        #endregion

        #region Helper classes

        #region Recycle directory callback

        /// <summary>
        /// Index area entry recycle callback.
        /// </summary>
        private class RemoveDirectoryCallback : Object, IEntryRecycleCallback
        {
            #region Properties

            /// <summary>
            /// Contains the directory to delete from its parent.
            /// </summary>
            private readonly Directory FDirectory;

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="RemoveDirectoryCallback"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="RemoveDirectoryCallback"/>.
            /// </summary>
            /// <param name="directory">The directory to eventually remove.</param>
            [DebuggerStepThrough]
            public RemoveDirectoryCallback(Directory directory)
                : base()
            {
                FDirectory = directory;
            }
            #endregion

            #region IEntryRecycleCallback Members

            /// <summary>
            /// Called when the deleted entry is recycled.
            /// </summary>
            [DebuggerStepThrough]
            void IEntryRecycleCallback.OnRecycled()
            {
                Debug.WriteLine(String.Format("Recycling entries for directory {0}", FDirectory.GetFullPath()));
                Debug.Assert(FDirectory.FParent != null);
                FDirectory.FParent.FDirectories.Remove(FDirectory);
                FDirectory.FParent.FNameLookup.Remove(FDirectory.Name);
                // Mark as non-resurrectable.
                FDirectory.Invalidate();
            }

            #endregion
        }

        #endregion

        #region Recycle file callback

        /// <summary>
        /// Index area entry recycle callback.
        /// </summary>
        private class RemoveFileCallback : Object, IEntryRecycleCallback
        {
            #region Properties

            /// <summary>
            /// Contains the file to delete from its parent.
            /// </summary>
            private readonly File FFile;

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="RemoveFileCallback"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="RemoveFileCallback"/>.
            /// </summary>
            /// <param name="file">The file to eventually remove.</param>
            [DebuggerStepThrough]
            public RemoveFileCallback(File file)
                : base()
            {
                FFile = file;
            }
            #endregion

            #region IEntryRecycleCallback Members

            /// <summary>
            /// Called when the deleted entry is recycled.
            /// </summary>
            [DebuggerStepThrough]
            void IEntryRecycleCallback.OnRecycled()
            {
                Debug.WriteLine(String.Format("Recycling entries for file {0}", FFile.GetFullPath()));
                Debug.Assert(FFile.Parent != null);
                FFile.Parent.FFiles.Remove(FFile);
                FFile.Parent.FNameLookup.Remove(FFile.Name);
                // Mark as non-resurrectable.
                FFile.Invalidate();
            }

            #endregion
        }

        #endregion

        #region Relocate callback

        /// <summary>
        /// Index area entry relocation callback.
        /// </summary>
        private class RelocateCallback : Object, IEntryMoveCallback
        {
            #region Properties

            /// <summary>
            /// Contains the list of directories that can be relocated.
            /// </summary>
            private readonly System.Collections.Generic.Dictionary<Int32, Directory> FDirectories = new System.Collections.Generic.Dictionary<Int32, Directory>();

            #endregion

            #region Methods

            /// <summary>
            /// Collects directories and files.
            /// </summary>
            /// <param name="directory">The directory to start with.</param>
            [DebuggerStepThrough]
            public void Collect(Directory directory)
            {
                if (directory.FIndexAreaEntryIndex > 0)
                {
                    FDirectories.Add(directory.FIndexAreaEntryIndex, directory);
                }
                for (int i = 0; i < directory.FDirectories.Count; i++)
                {
                    Collect(directory.FDirectories[i]);
                }
            }

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="RelocateCallback"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="RelocateCallback"/>.
            /// </summary>
            [DebuggerStepThrough]
            public RelocateCallback()
                : base()
            {
            }
            #endregion

            #region IEntryMoveCallback Members

            /// <summary>
            /// Called when the entry is relocated.
            /// </summary>
            /// <param name="prev">The previous index area entry index.</param>
            /// <param name="current">The current index area entry index.</param>
            [DebuggerStepThrough]
            void IEntryMoveCallback.OnRelocated(int prev, int current)
            {
                Directory directory;
                if (FDirectories.TryGetValue(prev, out directory))
                {
                    directory.FIndexAreaEntryIndex = current;
                    //FDirectories.Remove(prev);
                }
            }

            #endregion
        }

        #endregion

        #region Invalidate or resurrect directory

        /// <summary>
        /// Invalidates a file entry or resurrects/reuses a directory entry.
        /// </summary>
        private class InvalidateOrResurrectDirectory : Object, IFileSystemObjectInternalVisitor
        {
            #region Properties

            /// <summary>
            /// Contains the parent instance.
            /// </summary>
            private readonly Directory FParent;
            /// <summary>
            /// Contains the directory created.
            /// </summary>
            private Directory FResult;

            #endregion

            #region Methods

            /// <summary>
            /// Gets the directory created.
            /// </summary>
            /// <returns></returns>
            [DebuggerStepThrough]
            public Directory GetDirectory()
            {
                return FResult;
            }

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="InvalidateOrResurrectDirectory"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="InvalidateOrResurrectDirectory"/>.
            /// </summary>
            /// <param name="parent">The parent instance.</param>
            [DebuggerStepThrough]
            public InvalidateOrResurrectDirectory(Directory parent)
                : base()
            {
                FParent = parent;
            }
            #endregion

            #region IFileSystemObjectInternalVisitor

            /// <summary>
            /// Called by <see cref="Directory"/> objects.
            /// </summary>
            /// <param name="directory">The directory.</param>
            void IFileSystemObjectInternalVisitor.VisitDirectory(Directory directory)
            {
                // If not deleted, cant reuse it.
                if (!directory.IsDeleted)
                    throw new DirectoryAlreadyExistsException();
                // Deleted directory - undelete it.
                directory.Resurrect();
                FResult = directory;
            }

            /// <summary>
            /// Called by <see cref="File"/> objects.
            /// </summary>
            /// <param name="file">The file.</param>
            void IFileSystemObjectInternalVisitor.VisitFile(File file)
            {
                // If not deleted, cant create it.
                if (!file.IsDeleted)
                    throw new DirectoryAlreadyExistsException(file.GetFullPath());
                FParent.FNameLookup.Remove(file.Name);
                try
                {
                    // Retry request.
                    FResult = FParent.CreateDirectory(file.Name);
                    file.Invalidate();
                }
                catch
                {
                    FParent.FNameLookup.Add(file.Name, file);
                    throw;
                }
            }

            #endregion
        }

        #endregion

        #region Invalidate or resurrect file

        /// <summary>
        /// Invalidates a file entry or resurrects/reuses a file entry.
        /// </summary>
        private class InvalidateOrResurrectFile : Object, IFileSystemObjectInternalVisitor
        {
            #region Properties

            /// <summary>
            /// Contains the parent instance.
            /// </summary>
            private readonly Directory FParent;
            /// <summary>
            /// Contains the file created.
            /// </summary>
            private File FResult;

            #endregion

            #region Methods

            /// <summary>
            /// Gets the file created.
            /// </summary>
            /// <returns></returns>
            [DebuggerStepThrough]
            public File GetFile()
            {
                return FResult;
            }

            #endregion

            #region Constructors
            /// <overloads>Initializes a new instance of <see cref="InvalidateOrResurrectFile"/>.</overloads>
            /// <summary>
            /// Initializes a new instance of <see cref="InvalidateOrResurrectFile"/>.
            /// </summary>
            /// <param name="parent">The parent instance.</param>
            [DebuggerStepThrough]
            public InvalidateOrResurrectFile(Directory parent)
                : base()
            {
                FParent = parent;
            }
            #endregion

            #region IFileSystemObjectInternalVisitor

            /// <summary>
            /// Called by <see cref="Directory"/> objects.
            /// </summary>
            /// <param name="directory">The directory.</param>
            void IFileSystemObjectInternalVisitor.VisitDirectory(Directory directory)
            {
                // If not deleted, cant create it.
                if (!directory.IsDeleted)
                    throw new FileAlreadyExistsException(directory.GetFullPath());
                FParent.FNameLookup.Remove(directory.Name);
                try
                {
                    // Retry request.
                    FResult = FParent.CreateFile(directory.Name, -1);
                    directory.Invalidate();
                }
                catch
                {
                    FParent.FNameLookup.Add(directory.Name, directory);
                    throw;
                }
            }

            /// <summary>
            /// Called by <see cref="File"/> objects.
            /// </summary>
            /// <param name="file">The file.</param>
            void IFileSystemObjectInternalVisitor.VisitFile(File file)
            {
                // If not deleted, cant reuse it.
                if (!file.IsDeleted)
                    throw new DirectoryAlreadyExistsException();
                // Deleted directory - undelete it.
                file.Resurrect();
                // TODO: set length.
                FResult = file;
            }

            #endregion
        }

        #endregion

        #endregion
    }
}
